iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
DevOps

欸你這週GO了嘛系列 第 29

[DAY29]Golang Graceful Shutdown優雅的關機(?)

  • 分享至 

  • xImage
  •  

小劇場一下
當自己寫好的萬人響應(?)的服務上線啦,每秒都破千人在request,
這時候發現乾,程式有罷格,會寫入錯誤資料,為了不被DBA用手刀打死,
趕快更版上新程式
~
這時侯會有一個問題
還在執行中的request怎麼辦?
A:管它去死
B:管它去死
C:通通去死~~YA


以上的情境如果發生在單純request在讀取資料來看,其實無傷大雅,
因為不會影響到資料的正確性,頂多是無回應或是http 500噴掉,
就是這個but!
如果今天微服務是某個大系統中的中間點,就會影響資料的正確性,
因為微服務是無法進行db的transaction,所以沒有辦法進行rollback讓資料恢復到一致性。

graceful shutdown就是先停止收request,然後把已經收下來的request全部執行完,再把服務關掉,
頂多是前一個服務會噴錯,而不是一堆不一致的資料,微服務的架構下,修資料是一件很可怕的事情。


實作Graceful shutdown

目前實作Graceful Shutdown以http服務為主,Golang在1.8版時提供了http的Shutdown,不需要使用額外的套件或是自行建立worker的機制。

以下範例會使用到channel阻塞的特性,http服務用Goroutine來啟動,主線程使用channel來監聽系統終止的訊號

package main

import (
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/DoGetByQueryString", func(c *gin.Context) {
		p1 := c.DefaultQuery("param1", "Default")
		p2 := c.Query("param2")
		c.JSON(http.StatusOK, gin.H{"param1": p1, "param2": p2})
	})

	//原本是用router.Run(),要使用net/http套件的shutdown的話,需要使用原生的ListenAndServe
	srv := &http.Server{
		Addr:    ":8787",
		Handler: router,
	}
	//新增一個channel,type是os.Signal
	ch := make(chan os.Signal, 1)
	//call goroutine啟動http server
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			fmt.Println("SERVER GG惹:", err)
		}
	}()
	//Notify:將系統訊號轉發至channel
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	//阻塞channel
	<-ch
	fmt.Println("Graceful Shutdown start")
}

如果用ctrl+c去強制關閉服務會發生什麼事呢?

Graceful Shutdown start

以上的code可以確認服務被終止時會做一些特定的邏輯,而不是收到終止訊息時就直接關掉服務

func main() {
	ctx := context.Background()
	router := gin.Default()
	router.GET("/DoGetByQueryString", func(c *gin.Context) {
		time.Sleep(20 * time.Second)
		p1 := c.DefaultQuery("param1", "Default")
		p2 := c.Query("param2")
		c.JSON(http.StatusOK, gin.H{"param1": p1, "param2": p2})
	})

	//原本是用router.Run(),要使用net/http套件的shutdown的話,需要使用原生的ListenAndServe
	srv := &http.Server{
		Addr:    ":8787",
		Handler: router,
	}
	//新增一個channel,type是os.Signal
	ch := make(chan os.Signal, 1)
	//call goroutine啟動http server
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			fmt.Println("SERVER GG惹:", err)
		}
	}()
	//Notify:將系統訊號轉發至channel
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	//阻塞channel
	<-ch
    
    //收到關機訊號時做底下的流程
	fmt.Println("Graceful Shutdown start - 1")
    //透過context.WithTimeout產生一個新的子context,它的特性是有生命週期,這邊是設定10秒
    //只要超過10秒就會自動發出Done()的訊息
	c, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	fmt.Println("Graceful Shutdown start - 2")
    //使用net/http的shutdown進行關閉http server,參數是上面產生的子context,會有生命週期10秒,
    //所以10秒內要把request全都消化掉,如果超時一樣會強制關閉,所以如果http server要處理的是
    //需要花n秒才能處理的request就要把timeout時間拉長一點
	if err := srv.Shutdown(c); err != nil {
		log.Println("srv.Shutdown:", err)
	}
    //使用select去阻塞主線程,當子context發出Done()的訊號才繼續向下走
	select {    
	case <-c.Done():
		fmt.Println("Graceful Shutdown start - 3")
		close(ch)
	}
	fmt.Println("Graceful Shutdown end ")
}

GRPC版本的叫GracefulStop

做法類似http server的版本,一樣是監聽關機訊號,然後把grpc server執行GracefulStop()就好了,
如果有興趣可以看這篇文章 grpc.GracefulStop()

使用DAY20範例做魔改

func main() {
	ch := make(chan os.Signal, 1)

	go httpServer()

	apiListener, err := net.Listen("tcp", ":8787")
	if err != nil {
		panic(err)
	}

	grpc := grpc.NewServer(
		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(

			grpc_recovery.UnaryServerInterceptor(),
		)),
	)

	gw.RegisterSaySomethingServiceServer(grpc, newSaySomethingServiceServer())

	reflection.Register(grpc)

	if err = grpc.Serve(apiListener); err != nil {
		panic(err)
	}
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	<-ch
	c, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
    //加入這段,只是因為使用GRPC GATEWAY,會多了一段http server的部份就是了,基本上GRPC SERVER關
    //掉也無法打過來就是了
	grpc.GracefulStop()
	select {
	case <-c.Done():
		close(ch)
	}
	fmt.Println("Graceful Shutdown end ")

}

上一篇
[DAY28]Goroutine的好碰友-Channel
下一篇
[DAY30]完賽囉~~聊聊context
系列文
欸你這週GO了嘛30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言